﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *
 **/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Xml;
using System.Xml.Linq;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using Peter.Common;
using Peter.Common.Dialog;
using Peter.Common.Interfaces;
using Peter.Common.Services;
using Peter.Common.Utilities;
using Peter.Data;
using Peter.Logic.Dialogs;
using Peter.Logic.EventArguments;
using Peter.Logic.Interfaces;
using Peter.Properties;
using Xceed.Wpf.AvalonDock.Layout.Serialization;

namespace Peter.Logic
{
   /// <summary>
   /// Represents the main view model for peter.
   /// </summary>
   public class ModelPeterMain : MainWindowViewModelBase, IPeter, IDisposable
   {
      #region Fields

      private string m_Title;
      private string m_CurrentMemory;
      private Window m_MainWin;
      private object m_ActiveContent;
      private int m_ActiveModelEditors;
      private ITimerService m_Timer;
      private ModelMenu m_MenuModel;
      private ModelStatus m_StatusModel;
      private bool m_ShowNotificationBar;
      private ModelPlugins m_PluginModel;
      private ModelQuickOpen m_QuickOpen;
      private PeterFileWatcher m_FileWatcher;
      private const string APP_TITLE = "Peter";
      private ModelProjectManager m_ProjectManager;
      private ModelCodeExplorer m_CodeExplorer;
      private ModelFileExplorer m_FileExplorer;
      private ModelApplicationOutput m_AppOutput;
      private ModelContextMenu m_ContextMenuModel;
      private SearchAutoComplete m_SearchAutoComplete;
      private readonly ObservableCollection<IPeterDocument> m_Documents = new ObservableCollection<IPeterDocument> ();
      private readonly ObservableCollection<IPeterToolWindow> m_ToolWindows = new ObservableCollection<IPeterToolWindow> ();

      #endregion

      #region Events

      /// <summary>
      /// Event that occurs when the application is shutting down.
      /// </summary>
      public event EventHandler ShuttingDown;

      /// <summary>
      /// Event that occurs when the active content in Peter has changed.
      /// </summary>
      public event EventHandler ActiveContentChanged;

      #endregion

      /// <summary>
      /// Initializes a new main model.
      /// </summary>
      public void Initialize ()
      {
         this.Title = APP_TITLE;
         this.DialogVisibility = Visibility.Collapsed;
         ServiceManager.AddSingleton<IPeter, ModelPeterMain> (this);

         LoadHighlighting ();
         this.MenuModel = new ModelMenu ();
         this.StatusModel = new ModelStatus ();
         this.m_ProjectManager = new ModelProjectManager ();
         this.m_AppOutput = new ModelApplicationOutput ();
         this.ContextMenuModel = new ModelContextMenu ();

         this.m_PluginModel = new ModelPlugins ();
         ServiceManager.AddSingleton<IPluginManager, ModelPlugins> (this.m_PluginModel);
         this.m_PluginModel.Load ();
         this.m_PluginModel.Compose (this);
         this.m_PluginModel.Compose (this.m_MenuModel);
         this.m_PluginModel.Compose (this.m_ProjectManager);

         this.OptionsModel = new ModelOptions ();
         ServiceManager.AddSingleton<IPeterOptions, ModelOptions> (this.OptionsModel);
         this.m_PluginModel.Compose (this.OptionsModel);
         this.m_PluginModel.OptionsLoaded ();

         this.Documents.CollectionChanged += DocumentsCollectionChanged;

         this.HandleDroppedFiles (ArgumentService.Instance.Arguments.ToArray ());
         ArgumentService.Instance.ArgumentAdded += this.OnAppArgument;

         this.m_Timer = ServiceManager.Get<ITimerService> ();
         this.m_Timer.Interval = 1000;
         this.m_Timer.OnTimerTick += this.OnTimerTick;
         this.m_Timer.Start ();
      }

      /// <summary>
      /// Gets the quick open model.
      /// </summary>
      [Import (typeof (ModelQuickOpen))]
      public ModelQuickOpen QuickOpen
      {
         get { return this.m_QuickOpen; }
         set
         {
            this.m_QuickOpen = value;
            this.RaisePropertyChanged ("QuickOpen");
         }
      }

      /// <summary>
      /// Gets the main instance of peter.
      /// </summary>
      [Export (typeof (IPeter))]
      public IPeter Instance
      {
         get { return this; }
      }

      /// <summary>
      /// Gets the search auto complete.
      /// </summary>
      [Export (typeof (IPeterSearchAutoComplete))]
      public IPeterSearchAutoComplete SearchAutoComplete
      {
         get { return this.m_SearchAutoComplete ?? (this.m_SearchAutoComplete = new SearchAutoComplete ()); }
      }

      /// <summary>
      /// Gets the peter file watcher.
      /// </summary>
      [Export (typeof (IPeterFileWatcher))]
      public IPeterFileWatcher FileWatcher { get { return this.m_FileWatcher ?? (this.m_FileWatcher = new PeterFileWatcher ()); } }

      /// <summary>
      /// Initializes command bindings.
      /// </summary>
      public ModelPeterMain ()
      {
         this.InitializeCommandBindings ();
      }

      /// <summary>
      /// Gets or Sets if the notification bar should be shown or not.
      /// </summary>
      public bool ShowNotificationBar
      {
         get { return this.m_ShowNotificationBar; }
         set
         {
            if (this.m_ShowNotificationBar != value)
            {
               this.m_ShowNotificationBar = value;
               this.RaisePropertyChanged ("ShowNotificationBar");
            }
         }
      }

      /// <summary>
      /// Gets the options model
      /// </summary>
      [Export (typeof (IPeterOptions))]
      public ModelOptions OptionsModel { get; set; }

      /// <summary>
      /// Gets the list of plugins.
      /// </summary>
      [ImportMany (typeof (PeterDockItem))]
      public IEnumerable<Lazy<PeterDockItem, IDictionary<string, object>>> PluginDockContents { get; set; }

      /// <summary>
      /// Occurs whenever the collection of Documents changed.
      /// </summary>
      /// <param name="sender">Dockgroup object.</param>
      /// <param name="e">EventArgs</param>
      private void DocumentsCollectionChanged (object sender, EventArgs e)
      {
         ActiveModelEditorsOnDockGroup ();
      }

      /// <summary>
      /// Checks if there are active editors for the application. If there are none, the title of application is set to default.
      /// </summary>
      private void ActiveModelEditorsOnDockGroup ()
      {
         this.ActiveModelEditors = this.Documents.OfType<ModelEditor> ().Count ();
         if (this.ActiveModelEditors == 0)
         {
            this.Title = APP_TITLE;
         }
      }

      /// <summary>
      /// Gets the main peter window.
      /// </summary>
      private Window MainWindow
      {
         get
         {
            if (this.m_MainWin == null)
            {
               var appMain = ServiceManager.Get<IAppMain> ();
               this.m_MainWin = appMain.MainWindow;
            }
            return this.m_MainWin;
         }
      }

      /// <summary>
      /// Gets or Sets the current memory consumption.
      /// </summary>
      public string CurrentMemory
      {
         get { return this.m_CurrentMemory; }
         private set
         {
            if (this.m_CurrentMemory != value)
            {
               this.m_CurrentMemory = value;
               this.RaisePropertyChanged ("CurrentMemory");
            }
         }
      }

      /// <summary>
      /// Gets the list of documents.
      /// </summary>
      public ObservableCollection<IPeterDocument> Documents { get { return this.m_Documents; } }

      /// <summary>
      /// Gets the list of tool windows.
      /// </summary>
      public ObservableCollection<IPeterToolWindow> ToolWindows { get { return this.m_ToolWindows; } }

      /// <summary>
      /// Gets or Sets the number of active ModelEditor objects.
      /// </summary>
      public int ActiveModelEditors
      {
         get { return m_ActiveModelEditors; }
         set
         {
            m_ActiveModelEditors = value;
            RaisePropertyChanged ("ActiveModelEditors");
         }
      }

      /// <summary>
      /// Gets the version of Peter being used.
      /// </summary>
      public Version Version
      {
         get { return Assembly.GetExecutingAssembly ().GetName ().Version; }
      }

      /// <summary>
      /// Gets the active item as editable, null if not editable.
      /// </summary>
      public IEditable ActiveEditable
      {
         get { return this.ActiveContent as IEditable; }
      }

      /// <summary>
      /// Gets the active document object, null if object is not a document.
      /// </summary>
      public IDocument ActiveDocument
      {
         get { return this.ActiveContent as IDocument; }
      }

      /// <summary>
      /// Gets the current caret information, null if unavailable.
      /// </summary>
      public ICaret Caret
      {
         get { return this.ActiveContent as ICaret; }
      }

      /// <summary>
      /// Gets the menu model.
      /// </summary>
      public ModelMenu MenuModel
      {
         get { return this.m_MenuModel; }
         private set
         {
            this.m_MenuModel = value;
            this.RaisePropertyChanged ("MenuModel");
         }
      }

      /// <summary>
      /// Gets the context menu model.
      /// </summary>
      public ModelContextMenu ContextMenuModel
      {
         get { return this.m_ContextMenuModel; }
         private set
         {
            this.m_ContextMenuModel = value;
            this.RaisePropertyChanged ("ContextMenuModel");
         }
      }

      /// <summary>
      /// Gets or Sets the Status model.
      /// </summary>
      public ModelStatus StatusModel
      {
         get { return this.m_StatusModel; }
         private set
         {
            this.m_StatusModel = value;
            this.RaisePropertyChanged ("StatusModel");
         }
      }

      /// <summary>
      /// Gets or Sets the active content.
      /// </summary>
      public object ActiveContent
      {
         get { return this.m_ActiveContent; }
         set
         {
            if (this.m_ActiveContent != value)
            {
               this.m_ActiveContent = value;
               this.RaisePropertyChanged ("ActiveContent");
               this.RaisePropertyChanged ("ActiveEditable");
               this.RaisePropertyChanged ("ActiveDocument");
               this.RaisePropertyChanged ("Caret");
               this.UpdateTitle ();

               if (this.ActiveContentChanged != null)
                  this.ActiveContentChanged (this, EventArgs.Empty);
            }
         }
      }

      /// <summary>
      /// Gets or Sets the title of the application.
      /// </summary>
      public string Title
      {
         get { return this.m_Title; }
         set
         {
            if (this.m_Title != value)
            {
               this.m_Title = value;
               this.RaisePropertyChanged ("Title");
            }
         }
      }

      /// <summary>
      /// Updates the title of the application.
      /// </summary>
      public void UpdateTitle ()
      {
         var title = APP_TITLE;
         var content = this.ActiveContent as IDockItem;
         if (content != null)
         {
            title += " - " + content.Title;
         }
         this.Title = title;
      }

      /// <summary>
      /// Creates a new file.
      /// </summary>
      public void NewFile ()
      {
         this.OpenFile (string.Empty);
      }

      /// <summary>
      /// Registers the given key binding with the application for hot key support.
      /// </summary>
      /// <param name="binding">The key binding to register.</param>
      public void RegisterHotkey (KeyBinding binding)
      {
         try
         {
            this.MainWindow.InputBindings.Add (binding);
         }
         catch (Exception ex)
         {
            Console.Error.WriteLine (ex.Message);
         }
      }

      /// <summary>
      /// Shows the given dialog.
      /// </summary>
      /// <param name="dialogContent">Dialog content to show.</param>
      public void ShowDialog (DialogBase dialogContent)
      {
         if (dialogContent != null)
         {
            if (!dialogContent.IsComposed)
               this.m_PluginModel.Compose (dialogContent);
            this.DialogContent = dialogContent;
         }
      }

      /// <summary>
      /// Opens the given files.
      /// </summary>
      /// <param name="filePaths">A list of full file paths to open.</param>
      public void OpenFile (IEnumerable<string> filePaths)
      {
         foreach (var filePath in filePaths)
         {
            this.OpenFile (filePath);
         }
      }

      /// <summary>
      /// Opens the given file.
      /// </summary>
      /// <param name="filePath">The full path to the file to open.</param>
      public void OpenFile (string filePath)
      {
         if (string.IsNullOrEmpty (filePath))
         {
            CreateNewDocumentTab ();
         }
         else
         {
            var fileInfo = new FileInfo (filePath);
            PeterCommon.AddRecentFile (fileInfo);
            bool documentOpened = CheckOpenedDocument (fileInfo);
            if (!documentOpened)
            {
               if (IsImage (fileInfo))
                  this.CreateImageTab (fileInfo);
               else if (IsZip (fileInfo))
                  this.CreateZipTab (fileInfo);
               else
                  CreateNewDocumentTab (fileInfo);
            }
         }
      }

      /// <summary>
      /// Checks to see if the given file is a zip file.
      /// </summary>
      /// <param name="fileInfo">File object to check.</param>
      /// <returns>True if file is a zip file, otherwise false.</returns>
      private static bool IsZip (FileSystemInfo fileInfo)
      {
         return fileInfo.Extension.Equals (".zip", StringComparison.CurrentCultureIgnoreCase);
      }

      /// <summary>
      /// Checks to see if the given file is an image.
      /// </summary>
      /// <param name="fileInfo">File object to check.</param>
      /// <returns>True if file is an image, otherwise false.</returns>
      private static bool IsImage (FileSystemInfo fileInfo)
      {
         return fileInfo.Extension.Equals (".jpg", StringComparison.CurrentCultureIgnoreCase) ||
            fileInfo.Extension.Equals (".jpeg", StringComparison.CurrentCultureIgnoreCase) ||
            fileInfo.Extension.Equals (".jpe", StringComparison.CurrentCultureIgnoreCase) ||
            fileInfo.Extension.Equals (".bmp", StringComparison.CurrentCultureIgnoreCase) ||
            fileInfo.Extension.Equals (".png", StringComparison.CurrentCultureIgnoreCase) ||
            fileInfo.Extension.Equals (".tiff", StringComparison.CurrentCultureIgnoreCase) ||
            fileInfo.Extension.Equals (".tif", StringComparison.CurrentCultureIgnoreCase) ||
            fileInfo.Extension.Equals (".gif", StringComparison.CurrentCultureIgnoreCase);
      }

      /// <summary>
      /// Creates a new tab for a zip file.
      /// </summary>
      /// <param name="file">The zip file object.</param>
      public void CreateZipTab (FileInfo file)
      {
         var zipExplorer = new ModelZipExplorer (file);
         this.AddDocument (zipExplorer);
         this.ActiveContent = zipExplorer;
         this.UpdateTitle ();
      }

      /// <summary>
      /// Creates a new tab for an image.
      /// </summary>
      /// <param name="file">The image's file object.</param>
      public void CreateImageTab (FileInfo file)
      {
         var image = new ModelImageViewer (file);
         this.AddDocument (image);
         this.ActiveContent = image;
         this.UpdateTitle ();
      }

      /// <summary>
      /// Creates a new document tab.
      /// </summary>
      /// <param name="fileInfo">The object related to the given file. Null if a new document is created.</param>
      private void CreateNewDocumentTab (FileInfo fileInfo = null)
      {
         var editor = new ModelEditor ();
         if (fileInfo != null)
            editor.Open (fileInfo);
         this.AddDocument (editor);
         UpdateTitle ();
         this.ActiveContent = editor;
      }

      /// <summary>
      /// Checks if a file is already opened in the editor.
      /// </summary>
      /// <param name="fileInfo">The full path of the file to check.</param>
      /// <returns></returns>
      private bool CheckOpenedDocument (FileInfo fileInfo)
      {
         bool documentOpened = false;
         var editorCollection = this.Documents;
         var openedFile = editorCollection.FirstOrDefault (editor =>
                             editor.FileInfo != null && editor.FileInfo.FullName.Equals (fileInfo.FullName));
         if (openedFile != null)
         {
            documentOpened = true;
            this.ActiveContent = openedFile;
         }

         return documentOpened;
      }

      /// <summary>
      /// Gets an enumeration of all the currently open content in Peter.
      /// </summary>
      /// <returns>All open content in Peter.</returns>
      public IEnumerable<IDockItem> GetAllOpenContent ()
      {
         return this.Documents.Cast<IDockItem> ().Concat (this.ToolWindows);
      }

      /// <summary>
      /// Adds a new item to the current document group 
      /// </summary>
      /// <param name="documentModel">The model of the item to add.</param>
      public void AddDocument (IPeterDocument documentModel)
      {
         if (!documentModel.IsComposed)
            this.m_PluginModel.Compose (documentModel);
         this.Documents.Add (documentModel);
      }

      /// <summary>
      /// Closes the document with the given model.
      /// </summary>
      /// <param name="documentModel">Model to close.</param>
      public void CloseDocument (IPeterDocument documentModel)
      {
         this.Documents.Remove (documentModel);
      }

      /// <summary>
      /// Closes the tool window with the given model.
      /// </summary>
      /// <param name="tooWindowModel">Model to close.</param>
      public void CloseToolWindow (IPeterToolWindow tooWindowModel)
      {
         this.ToolWindows.Remove (tooWindowModel);
      }

      /// <summary>
      /// Adds a tool window to the given dock spot.
      /// </summary>
      /// <param name="toolWindowModel">The model of the item to add.</param>
      public void AddToolWindow (IPeterToolWindow toolWindowModel)
      {
         if (!toolWindowModel.IsComposed)
            this.m_PluginModel.Compose (toolWindowModel);
         if (!this.ToolWindows.Contains (toolWindowModel))
            this.ToolWindows.Add (toolWindowModel);

         this.ActiveContent = toolWindowModel;
      }

      /// <summary>
      /// Shows the requested window.
      /// </summary>
      /// <param name="window">Window to show.</param>
      public void ShowToolWindow (PeterToolWindows window)
      {
         if (window == PeterToolWindows.FileExplorer)
         {
            if (this.m_FileExplorer == null)
               this.m_FileExplorer = new ModelFileExplorer ();
            this.AddToolWindow (this.m_FileExplorer);
         }
         else if (window == PeterToolWindows.CodeExplorer)
         {
            if (this.m_CodeExplorer == null)
               this.m_CodeExplorer = new ModelCodeExplorer ();
            this.AddToolWindow (this.m_CodeExplorer);
         }
         else if (window == PeterToolWindows.Output)
         {
            if (this.m_AppOutput == null)
               this.m_AppOutput = new ModelApplicationOutput ();
            this.AddToolWindow (this.m_AppOutput);
         }
         else if (window == PeterToolWindows.Workspace)
         {
            this.AddToolWindow (this.m_ProjectManager.Workspace);
         }
         else if (window == PeterToolWindows.Terminal)
         {
            this.AddToolWindow (new ModelTerminal ());
         }
      }

      /// <summary>
      /// Handles the dropped files in the main window.
      /// </summary>
      /// <param name="filePaths">The path(s) of the file(s) dropped.</param>
      public void HandleDroppedFiles (string [] filePaths)
      {
         foreach (var path in filePaths)
         {
            try
            {
               if (File.Exists (path))
               {
                  OpenFile (path);
               }
            }
            catch (Exception ex)
            {
               var messageBox = ServiceManager.Get<IMessageBoxService> ();
               if (messageBox != null)
               {
                  messageBox.ShowError (String.Format ("The file {0} couldn't be opened - {1}", path, ex.Message));
               }
            }
         }
      }

      /// <summary>
      /// Shows the options tab.
      /// </summary>
      /// <param name="startingOptionPage">The options page to start up.</param>
      public void ShowOptions (object startingOptionPage = null)
      {
         if (startingOptionPage != null)
            this.OptionsModel.SelectedItem = startingOptionPage;

         if (!this.Documents.Contains (this.OptionsModel))
            this.AddDocument (this.OptionsModel);
         this.ActiveContent = this.OptionsModel;
      }

      /// <summary>
      /// Occurs when the event timer ticks.
      /// </summary>
      /// <param name="sender">Timer</param>
      /// <param name="e">EventArgs</param>
      private void OnTimerTick (object sender, EventArgs e)
      {
         // Gets the current memory consumption.
         var bytes = System.Diagnostics.Process.GetCurrentProcess ().WorkingSet64;
         this.CurrentMemory = string.Format ("{0:0} MB", bytes / 1048576);
      }

      #region Closing

      /// <summary>
      /// Called when the application is closing.
      /// </summary>
      /// <returns>True if ok to close, otherwise false.</returns>
      public bool Close ()
      {
         if (this.ShuttingDown != null)
            this.ShuttingDown (this, EventArgs.Empty);

         if (this.ActiveModelEditors != 0)
         {
            if (this.Documents.OfType<ModelEditor> ().Any (editor => !editor.ManageUnsavedContent ()))
            {
               return false;
            }
         }
         Settings.Default.Save ();
         this.Dispose ();
         return true;
      }

      /// <summary>
      /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
      /// </summary>
      /// <filterpriority>2</filterpriority>
      public void Dispose ()
      {
         Dispose (true);
         GC.SuppressFinalize (this);
      }

      /// <summary>
      /// Disposes the object.
      /// </summary>
      /// <param name="disposing">True to release managed resources.</param>
      private void Dispose (bool disposing)
      {
         if (disposing)
         {
            ArgumentService.Instance.ArgumentAdded -= this.OnAppArgument;
            if (this.m_PluginModel != null)
               this.m_PluginModel.Dispose ();
         }
      }

      #endregion

      #region Dock Manager Startup

      /// <summary>
      /// Occurs when there is a new application argument.
      /// This will happen on a different thread.
      /// </summary>
      /// <param name="sender">Event Sender.</param>
      /// <param name="e">ArgumentEventArgs</param>
      private void OnAppArgument (object sender, ArgumentEventArgs e)
      {
         var fileExists = File.Exists (e.Argument);
         this.GuiDispatcher.Invoke (new Action (() =>
            {
               if (this.MainWindow.WindowState == WindowState.Minimized)
                  this.MainWindow.WindowState = WindowState.Normal;
               this.MainWindow.Activate ();
               this.MainWindow.Topmost = true;
               this.MainWindow.Topmost = false;
               if (fileExists)
                  this.OpenFile (e.Argument);
               this.MainWindow.Focus ();
               Thread.Sleep (1000);
               ((ModelEditor) this.ActiveDocument).Editor.Focus ();
            }), null);
      }

      /// <summary>
      /// Occurs when the layout is being deserialized.
      /// </summary>
      /// <param name="sender">Dock manager.</param>
      /// <param name="e">LayoutSerializationCallbackEventArgs</param>
      public void DockDeserializationCallback (object sender, LayoutSerializationCallbackEventArgs e)
      {
         if (e.Model == null || e.Model.ContentId == null)
            return;

         if (e.Model.ContentId.StartsWith (Guids.EDITOR))
         {
            var editor = new ModelEditor ();
            if (e.Model.ContentId.Contains ("|"))
            {
               editor.Open (new FileInfo (e.Model.ContentId.Split ('|') [1]));
            }
            e.Content = editor;
            this.AddDocument (editor);
         }
         else if (e.Model.ContentId == Guids.FILE_EXPLORER)
         {
            this.ShowToolWindow (PeterToolWindows.FileExplorer);
            e.Content = this.m_FileExplorer;
         }
         else if (e.Model.ContentId == Guids.CODE_EXPLORER)
         {
            this.ShowToolWindow (PeterToolWindows.CodeExplorer);
            e.Content = this.m_CodeExplorer;
         }
         else if (e.Model.ContentId == Guids.OUTPUT)
         {
            this.ShowToolWindow (PeterToolWindows.Output);
            e.Content = this.m_AppOutput;
         }
         else if (e.Model.ContentId == Guids.TERMINAL)
         {
            this.ShowToolWindow (PeterToolWindows.Terminal);
            e.Content = this.ToolWindows.Last (win => win is ModelTerminal);
         }
         else if (e.Model.ContentId == Guids.WORKSPACE)
         {
            this.ShowToolWindow (PeterToolWindows.Workspace);
            e.Content = this.m_ProjectManager.Workspace;
         }
         else if (e.Model.ContentId == Guids.OPTIONS)
         {
            this.ShowOptions ();
            e.Content = this.OptionsModel;
         }
         else if (e.Model.ContentId.StartsWith (Guids.IMAGE_VIEWER))
         {
            var imageView = new ModelImageViewer (new FileInfo (e.Model.ContentId.Split ('|') [1]));
            e.Content = imageView;
            this.AddDocument (imageView);
         }
         else if (e.Model.ContentId.StartsWith (Guids.ZIP_EXPLORER))
         {
            var imageView = new ModelZipExplorer (new FileInfo (e.Model.ContentId.Split ('|') [1]));
            e.Content = imageView;
            this.AddDocument (imageView);
         }
         else
         {
            var dockContent = this.PluginDockContents.FirstOrDefault (dc => dc.Metadata.ContainsKey ("ContentId") &&
               dc.Metadata ["ContentId"].ToString () == e.Model.ContentId);
            if (dockContent != null)
            {
               if (dockContent.Value is IPeterToolWindow)
                  this.AddToolWindow (dockContent.Value as IPeterToolWindow);
               else
                  this.AddDocument (dockContent.Value as IPeterDocument);
               e.Content = dockContent.Value;
            }
            else
            {
               var editor = new ModelEditor ();
               e.Content = editor;
               this.AddDocument (editor);
            }
         }
      }

      #endregion

      #region Highlighting (Could be move to a separate VM)

      /// <summary>
      /// Loads all known highlighting.
      /// </summary>
      private static void LoadHighlighting ()
      {
         var dir = CommonUtilities.GetAssemblyDirectory ();
         dir = Path.Combine (dir, "AvalonEdit.Highlighting\\");
         var highlightingFiles = Directory.GetFiles (dir).ToList ();

         LoadHighlighting (dir + "XmlDoc.xshd", highlightingFiles);
         LoadHighlighting (dir + "CSharp-Mode.xshd", highlightingFiles);

         LoadHighlighting (dir + "JavaScript-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "HTML-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "ASPX.xshd", highlightingFiles);

         LoadHighlighting (dir + "Ruby.xshd", highlightingFiles);
         LoadHighlighting (dir + "Ruby_Html.xshd", highlightingFiles);
         LoadHighlighting (dir + "Batch.xshd", highlightingFiles);

         LoadHighlighting (dir + "Boo.xshd", highlightingFiles);
         LoadHighlighting (dir + "Coco-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "CSS-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "CPP-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "Java-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "Patch-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "PowerShell.xshd", highlightingFiles);
         LoadHighlighting (dir + "PHP-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "Tex-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "VB-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "XML-Mode.xshd", highlightingFiles);
         LoadHighlighting (dir + "MarkDown-Mode.xshd", highlightingFiles);

         // Add any additional highlighting files...
         foreach (var highlightingFile in highlightingFiles)
         {
            LoadHighlighting (highlightingFile, null);
         }
      }

      /// <summary>
      /// Loads the given highlighting.
      /// </summary>
      /// <param name="file">Highlighting file.</param>
      /// <param name="highlightingFiles">The current list of highlighting files.</param>
      private static void LoadHighlighting (string file, IList<string> highlightingFiles)
      {
         if (highlightingFiles != null)
            highlightingFiles.RemoveIgnoreCase (file);
         try
         {
            var root = XElement.Load (file);
            var name = root.Attribute ("name").Value;
            var extensionAtt = root.Attribute ("extensions");
            string [] ext = null;
            if (extensionAtt != null)
               ext = extensionAtt.Value.Split (';');
            using (var reader = new XmlTextReader (file))
            {
               var highlighting = HighlightingLoader.Load (reader, HighlightingManager.Instance);
               HighlightingManager.Instance.RegisterHighlighting (name, ext, highlighting);
            }
         }
         catch (Exception ex)
         {
            Console.Error.WriteLine ("Error when loading Syntax Highlighting '{0}': {1}", file, ex.Message);
         }
      }

      #endregion

      #region Commands

      /// <summary>
      /// Gets the command bindings for application commands.
      /// </summary>
      public Collection<CommandBinding> CommandBindings { get; private set; }

      /// <summary>
      /// Gets the input bindings for application commands.
      /// </summary>
      public Collection<InputBinding> InputBindings { get; private set; }

      /// <summary>
      /// Initializes the command binding.
      /// </summary>
      private void InitializeCommandBindings ()
      {
         CommandBindings = new Collection<CommandBinding> ();
         InputBindings = new Collection<InputBinding> ();

         CommandBindings.Add (new CommandBinding (ApplicationCommands.New, OnNew));
         CommandBindings.Add (new CommandBinding (ApplicationCommands.Open, OnOpen));
         CommandBindings.Add (new CommandBinding (ApplicationCommands.Close, OnClose));

         CommandBindings.Add (new CommandBinding (PeterApplicationCommands.SaveAll, OnSaveAll));
         CommandBindings.Add (new CommandBinding (PeterApplicationCommands.NewProject, OnNewProject));
         CommandBindings.Add (new CommandBinding (PeterApplicationCommands.OpenProject, OnOpenProject));

         CommandBindings.Add (new CommandBinding (PeterApplicationCommands.ShowAbout, OnShowAbout));
         CommandBindings.Add (new CommandBinding (PeterApplicationCommands.ShowOptions, OnShowOptions));
         CommandBindings.Add (new CommandBinding (PeterApplicationCommands.ShowToolWindow, OnShowToolWindow));
         CommandBindings.Add (new CommandBinding (PeterApplicationCommands.OpenFile, OnOpenFile));

         CommandBindings.Add (new CommandBinding (PeterInternalCommands.ChangeHighlighting, OnChangeHighlighting,
                                                  CanChangeHighlighting));

         this.AddEditAction (ApplicationCommands.Save, EditAction.Save);
         this.AddEditAction (ApplicationCommands.SaveAs, EditAction.SaveAs);
         this.AddEditAction (ApplicationCommands.Redo, EditAction.Redo);
         this.AddEditAction (ApplicationCommands.Undo, EditAction.Undo);
         this.AddEditAction (ApplicationCommands.Cut, EditAction.Cut);
         this.AddEditAction (ApplicationCommands.Copy, EditAction.Copy);
         this.AddEditAction (ApplicationCommands.Paste, EditAction.Paste);
         this.AddEditAction (ApplicationCommands.SelectAll, EditAction.SelectAll);
         this.AddEditAction (PeterInternalCommands.Format, EditAction.Format);
      }

      /// <summary>
      /// Checks to see if a file can be opened.
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private static void CanOpenFile (object sender, CanExecuteRoutedEventArgs e)
      {
         e.CanExecute = true;
      }

      private void OnOpenFile (object sender, ExecutedRoutedEventArgs e)
      {
         var fileToOpen = string.Empty;
         var fileInfo = e.Parameter as FileInfo;
         if (fileInfo != null)
            fileToOpen = fileInfo.FullName;
         if (string.IsNullOrEmpty (fileToOpen))
         {
            fileToOpen = e.Parameter.ToString ();
         }

         if (!string.IsNullOrEmpty (fileToOpen))
            this.OpenFile (fileToOpen);
      }

      /// <summary>
      /// Adds an edit action to the command binding.
      /// </summary>
      /// <param name="command">Command to associate binding with.</param>
      /// <param name="action">Edit action.</param>
      private void AddEditAction (ICommand command, EditAction action)
      {
         CommandBindings.Add (new CommandBinding (command,
                                                 (sender, args) => this.OnActiveEditable (action),
                                                 (sender, args) => args.CanExecute = this.CanActiveEditable (action)));
      }

      /// <summary>
      /// Executes the new command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnNew (object sender, ExecutedRoutedEventArgs e)
      {
         this.NewFile ();
      }

      /// <summary>
      /// Command handler for the open command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnOpen (object sender, ExecutedRoutedEventArgs e)
      {
         var open = ServiceManager.Get<IOpenFileService> ();
         open.AllowMultipleSelection = true;
         open.Filter = "*.*|*.*";
         if (open.ShowDialog ())
         {
            this.OpenFile (open.FileNames);
         }
      }

      /// <summary>
      /// Command handler for the close command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnClose (object sender, ExecutedRoutedEventArgs e)
      {
         var app = ServiceManager.Get<IAppMain> ();
         if (app != null)
            app.MainWindow.Close ();
      }

      /// <summary>
      /// Executes the save all command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnSaveAll (object sender, ExecutedRoutedEventArgs e)
      {
         var openContent = GetAllOpenContent ();
         foreach (var content in openContent)
         {
            var editable = content as IEditable;
            if (editable != null && editable.NeedsSaving)
            {
               editable.EditCommand.Execute (EditAction.Save);
            }
         }
         UpdateTitle ();
      }

      /// <summary>
      /// Executes the new project command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnNewProject (object sender, ExecutedRoutedEventArgs e)
      {
         this.m_ProjectManager.NewProject ();
      }

      /// <summary>
      /// Executes the open project command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnOpenProject (object sender, ExecutedRoutedEventArgs e)
      {
         this.m_ProjectManager.OpenProject ();
      }

      /// <summary>
      /// Executes the show about command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnShowAbout (object sender, ExecutedRoutedEventArgs e)
      {
         this.ShowDialog (new ModelAbout ());
      }

      /// <summary>
      /// Executes the show options command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnShowOptions (object sender, ExecutedRoutedEventArgs e)
      {
         this.ShowOptions (e.Parameter);
      }

      /// <summary>
      /// Executes the show options command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnShowToolWindow (object sender, ExecutedRoutedEventArgs e)
      {
         var window = (PeterToolWindows) e.Parameter;
         this.ShowToolWindow (window);
      }

      /// <summary>
      /// Checks if the given action can execute on the active editable.
      /// </summary>
      /// <param name="action">Action to check.</param>
      /// <returns>True if action can execute, otherwise false.</returns>
      private bool CanActiveEditable (EditAction action)
      {
         return this.ActiveEditable != null && ActiveEditable.EditCommand.CanExecute (action);
      }

      /// <summary>
      /// Checks if the given action can execute on the active editable.
      /// </summary>
      /// <param name="action">Action to check.</param>
      /// <returns>True if action can execute, otherwise false.</returns>
      private void OnActiveEditable (EditAction action)
      {
         this.ActiveEditable.EditCommand.Execute (action);
      }

      /// <summary>
      /// Can execute command handler for the change highlighting command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">CanExecuteRoutedEventArgs</param>
      private void CanChangeHighlighting (object sender, CanExecuteRoutedEventArgs e)
      {
         e.CanExecute = (this.ActiveEditable != null && this.ActiveContent is ModelEditor);
      }

      /// <summary>
      /// Command handler for the change highlighting command.
      /// </summary>
      /// <param name="sender">Issuer of the command.</param>
      /// <param name="e">ExecutedRoutedEventArgs</param>
      private void OnChangeHighlighting (object sender, ExecutedRoutedEventArgs e)
      {
         var editor = this.ActiveContent as ModelEditor;
         if (editor != null)
            editor.Editor.SetHighlighting (e.Parameter.ToString ());
      }

      #endregion
   }
}
